package rabbit.flt.test.cases;


import junit.framework.TestCase;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import rabbit.flt.common.Headers;
import rabbit.flt.common.context.TraceContext;
import rabbit.flt.common.trace.TraceData;
import rabbit.flt.test.common.mybatis.UserMapper;
import rabbit.flt.test.common.spi.TestTraceHandler;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Semaphore;

public abstract class BaseCases {

    protected Logger logger = LoggerFactory.getLogger(getClass());

    @Autowired
    protected UserMapper userMapper;

    protected void mybatisPlusTest() throws Exception {
        Class.forName("org.sqlite.JDBC");
        Connection connection = DriverManager.getConnection("jdbc:sqlite::resource:db/app.s3db");
        try {
            Statement stmt = connection.createStatement();
            String table = "user";
            dropTable(stmt, table);
            createTable(stmt, table);
            stmt.execute("insert into ".concat(table).concat(" (id, user_name) values('1', 'zhang3')"));
            Semaphore semaphore = new Semaphore(0);
            Map<String, TraceData> map = new ConcurrentHashMap<>();
            TestTraceHandler.setDiscardDataHandler(d -> {
                logger.info("{}#{}, {}", d.getTraceId(), d.getSpanId(), d.getNodeName());
                map.put(d.getSpanId(), d);
                semaphore.release();
            });
            TraceContext.openTrace("manual");
            userMapper.plusGetById("1");
            TraceContext.clearContext();
            semaphore.acquire();
            TestCase.assertEquals("plusGetById", map.get("0").getNodeName());
            TestTraceHandler.setDiscardDataHandler(null);
            stmt.close();
        } finally {
            connection.close();
        }
    }

    private void dropTable(Statement stmt, String table) {
        try {
            stmt.execute("drop table ".concat(table));
        } catch (Exception e) {

        }
    }

    private void createTable(Statement stmt, String table) throws SQLException {
        stmt.execute("create table ".concat(table).concat(" (id varchar(10), user_name varchar(10))"));
    }

    /**
     * 递归压栈测试
     */
    protected void pushStackTest() throws Exception {
        Semaphore semaphore = new Semaphore(0);
        List<TraceData> list = new ArrayList<>();
        TestTraceHandler.setDiscardDataHandler(t -> {
            if ("recursivelyAdd".equals(t.getNodeName()) || "count".equals(t.getNodeName())) {
                list.add(t);
                semaphore.release();
            }
        });
        CaseService caseService = new CaseService();
        int result = caseService.recursivelyAdd(3);
        TestCase.assertEquals(53, result);
        semaphore.acquire(6);
        TestCase.assertEquals("0", list.get(5).getSpanId());
        TestTraceHandler.setDiscardDataHandler(null);
    }

    protected void longTraceTest() throws Exception {
        Semaphore semaphore = new Semaphore(0);
        Set<String> names = new HashSet<>();
        List<String> list = new ArrayList<>();
        TestTraceHandler.setDiscardDataHandler(t -> {
            names.add(t.getNodeName());
            semaphore.release();
            if (t.isThresholdIndex()) {
                list.add("thresholdIndex");
            }
        });
        TestCase.assertFalse(TraceContext.isTraceOpened());
        CaseService caseService = new CaseService();
        caseService.longTrace(200);
        semaphore.acquire(100);
        TestCase.assertEquals(1, list.size());
        TestCase.assertTrue(names.contains("longTrace"));
        TestTraceHandler.setDiscardDataHandler(null);
        logger.info("用例【长链路trace数据量不会超过100个】验证成功");
    }

    protected void httpClientTest() throws Exception {
        Semaphore semaphore = new Semaphore(0);
        Map<String, TraceData> map = new ConcurrentHashMap<>();
        TestTraceHandler.setDiscardDataHandler(d -> {
            logger.info("traceData: {}#{}#{}#{}", d.getNodeName(), d.getSpanId(), d.getNodeDesc(), d.getApplicationCode());
            map.put(d.getSpanId(), d);
            semaphore.release();
        });
        CaseService caseService = new CaseService();
        caseService.doHttp3Request();
        semaphore.acquire(4);
        TestCase.assertEquals(4, map.size());
        TestCase.assertEquals("hello", map.get("0-0-0-0").getNodeName());
        TestCase.assertEquals("/mvc/hello", map.get("0-0-0").getNodeName());
        Map<String, Object> headers = map.get("0-0-0").getHttpRequest().getHeaders();
        TestCase.assertTrue(headers.containsKey(Headers.TRACE_ID.toLowerCase()) || headers.containsKey(Headers.TRACE_ID));
        TestCase.assertTrue(headers.containsKey(Headers.SPAN_ID.toLowerCase()) || headers.containsKey(Headers.SPAN_ID));
        TestCase.assertEquals("HTTP_CLIENT3", map.get("0-0").getNodeName());
        TestCase.assertEquals("doHttp3Request", map.get("0").getNodeName());

        caseService.doHttp4Request();
        semaphore.acquire(4);
        TestCase.assertEquals(4, map.size());
        TestCase.assertEquals("hello", map.get("0-0-0-0").getNodeName());
        TestCase.assertEquals("/mvc/hello", map.get("0-0-0").getNodeName());
        headers = map.get("0-0-0").getHttpRequest().getHeaders();
        TestCase.assertTrue(headers.containsKey(Headers.TRACE_ID.toLowerCase()) || headers.containsKey(Headers.TRACE_ID));
        TestCase.assertTrue(headers.containsKey(Headers.SPAN_ID.toLowerCase()) || headers.containsKey(Headers.SPAN_ID));
        TestCase.assertEquals("HTTP_CLIENT4", map.get("0-0").getNodeName());
        TestCase.assertEquals("doHttp4Request", map.get("0").getNodeName());
        TestCase.assertTrue(map.get("0-0").getHttpRequest().getHeaders().containsKey("name"));
        // http client4未记录
        TestCase.assertTrue(map.get("0-0").getHttpRequest().getRequestParameters().isEmpty());
        TestCase.assertTrue(!map.get("0-0").getHttpResponse().getHeaders().isEmpty());

        String responseBody = caseService.http4Error();
        TestCase.assertEquals("error", responseBody);
        semaphore.acquire(4);
        TestCase.assertEquals(4, map.size());
        TestCase.assertEquals("error", map.get("0-0-0-0").getNodeName());
        TestCase.assertTrue(map.get("0-0-0-0").getData().contains("rabbit.flt.common.exception.FltException: error"));
        TestCase.assertTrue(map.get("0-0-0-0").getStatus() == TraceData.Status.ERR);
        TestCase.assertEquals("/mvc/error", map.get("0-0-0").getNodeName());
        TestCase.assertEquals(responseBody, map.get("0-0-0").getHttpResponse().getBody());
        headers = map.get("0-0-0").getHttpRequest().getHeaders();
        TestCase.assertTrue(headers.containsKey(Headers.TRACE_ID.toLowerCase()) || headers.containsKey(Headers.TRACE_ID));
        TestCase.assertTrue(headers.containsKey(Headers.SPAN_ID.toLowerCase()) || headers.containsKey(Headers.SPAN_ID));
        TestCase.assertEquals("HTTP_CLIENT4", map.get("0-0").getNodeName());
        TestCase.assertEquals(responseBody, map.get("0-0").getHttpResponse().getBody());
        TestCase.assertEquals("http4Error", map.get("0").getNodeName());

        // 返回异常的body
        responseBody = caseService.http3Error();
        TestCase.assertEquals("error", responseBody);
        semaphore.acquire(4);
        TestCase.assertEquals(4, map.size());
        TestCase.assertEquals("error", map.get("0-0-0-0").getNodeName());
        TestCase.assertTrue(map.get("0-0-0-0").getData().contains("rabbit.flt.common.exception.FltException: error"));
        TestCase.assertTrue(map.get("0-0-0-0").getStatus() == TraceData.Status.ERR);
        TestCase.assertEquals("/mvc/error", map.get("0-0-0").getNodeName());

        // 验证trace读取异常body
        TestCase.assertEquals(responseBody, map.get("0-0-0").getHttpResponse().getBody());
        headers = map.get("0-0-0").getHttpRequest().getHeaders();
        TestCase.assertTrue(headers.containsKey(Headers.TRACE_ID.toLowerCase()) || headers.containsKey(Headers.TRACE_ID));
        TestCase.assertTrue(headers.containsKey(Headers.SPAN_ID.toLowerCase()) || headers.containsKey(Headers.SPAN_ID));
        TestCase.assertEquals("HTTP_CLIENT3", map.get("0-0").getNodeName());

        // 验证trace读取异常body
        TestCase.assertEquals(responseBody, map.get("0-0").getHttpResponse().getBody());
        TestCase.assertEquals("http3Error", map.get("0").getNodeName());


        TestTraceHandler.setDiscardDataHandler(null);
    }

    protected void unhandledHttpErrorTest() throws Exception {
        Semaphore semaphore = new Semaphore(0);
        Map<String, TraceData> map = new ConcurrentHashMap<>();
        TestTraceHandler.setDiscardDataHandler(d -> {
            logger.info("traceData: {}#{}#{}#{}", d.getNodeName(), d.getSpanId(), d.getNodeDesc(), d.getApplicationCode());
            map.put(d.getSpanId(), d);
            semaphore.release();
        });
        CaseService caseService = new CaseService();
        caseService.unHandledErrorRequest();
        semaphore.acquire(4);
        TestCase.assertEquals(4, map.size());
        TestCase.assertEquals("unHandledError", map.get("0-0-0-0").getNodeName());
        TestCase.assertEquals("/mvc/unHandledError", map.get("0-0-0").getNodeName());
        TestCase.assertEquals("HTTP_CLIENT3", map.get("0-0").getNodeName());
        TestCase.assertTrue(map.get("0-0").getHttpResponse().getBody().contains("500"));
        TestCase.assertEquals("unHandledErrorRequest", map.get("0").getNodeName());
        TestTraceHandler.setDiscardDataHandler(null);
    }

    /**
     * 直接返回错误
     *
     * @throws Exception
     */
    protected void redirectErrorTest() throws Exception {
        Semaphore semaphore = new Semaphore(0);
        Map<String, TraceData> map = new ConcurrentHashMap<>();
        TestTraceHandler.setDiscardDataHandler(d -> {
            logger.info("traceData: {}#{}#{}#{}", d.getNodeName(), d.getSpanId(), d.getNodeDesc(), d.getApplicationCode());
            map.put(d.getSpanId(), d);
            semaphore.release();
        });
        CaseService caseService = new CaseService();
        caseService.call("directError");
        semaphore.acquire(4);
        TestCase.assertEquals(4, map.size());
        TestCase.assertEquals("directError", map.get("0-0-0-0").getNodeName());
        TestCase.assertEquals("/mvc/directError", map.get("0-0-0").getNodeName());
        TestCase.assertEquals("direct500", map.get("0-0-0").getHttpResponse().getBody());
        TestCase.assertEquals("HTTP_CLIENT4", map.get("0-0").getNodeName());
        TestCase.assertEquals("direct500", map.get("0-0").getHttpResponse().getBody());
        TestCase.assertEquals("call", map.get("0").getNodeName());
        TestTraceHandler.setDiscardDataHandler(null);
    }


    protected void redirectError2Test() throws Exception {
        Semaphore semaphore = new Semaphore(0);
        Map<String, TraceData> map = new ConcurrentHashMap<>();
        TestTraceHandler.setDiscardDataHandler(d -> {
            logger.info("traceData: {}#{}#{}#{}", d.getNodeName(), d.getSpanId(), d.getNodeDesc(), d.getApplicationCode());
            map.put(d.getSpanId(), d);
            semaphore.release();
        });
        CaseService caseService = new CaseService();
        caseService.call("directError2");
        semaphore.acquire(4);
        TestCase.assertEquals(4, map.size());
        TestCase.assertEquals("directError2", map.get("0-0-0-0").getNodeName());
        TestCase.assertEquals("/mvc/directError2", map.get("0-0-0").getNodeName());
        TestCase.assertEquals("out by stream", map.get("0-0-0").getHttpResponse().getBody());
        TestCase.assertEquals("HTTP_CLIENT4", map.get("0-0").getNodeName());
        TestCase.assertEquals("out by stream", map.get("0-0").getHttpResponse().getBody());
        TestCase.assertEquals("call", map.get("0").getNodeName());
        TestTraceHandler.setDiscardDataHandler(null);
    }

    protected void redirectError3Test() throws Exception {
        Semaphore semaphore = new Semaphore(0);
        Map<String, TraceData> map = new ConcurrentHashMap<>();
        TestTraceHandler.setDiscardDataHandler(d -> {
            logger.info("traceData: {}#{}#{}#{}", d.getNodeName(), d.getSpanId(), d.getNodeDesc(), d.getApplicationCode());
            map.put(d.getSpanId(), d);
            semaphore.release();
        });
        CaseService caseService = new CaseService();
        caseService.call("directError3");
        semaphore.acquire(4);
        TestCase.assertEquals(4, map.size());
        TestCase.assertEquals("directError3", map.get("0-0-0-0").getNodeName());
        TestCase.assertEquals("/mvc/directError3", map.get("0-0-0").getNodeName());
        TestCase.assertEquals("out by writer", map.get("0-0-0").getHttpResponse().getBody());
        TestCase.assertEquals("HTTP_CLIENT4", map.get("0-0").getNodeName());
        TestCase.assertEquals("out by writer", map.get("0-0").getHttpResponse().getBody());
        TestCase.assertEquals("call", map.get("0").getNodeName());
        TestTraceHandler.setDiscardDataHandler(null);
    }
}
